#include <sstream>
#include <algorithm>
#include "icache/mem_cache.h"

namespace icache {

mem_cache::mem_cache(size_t quota) : image_loaded(false)
{
	_size = 0;
	_quota = quota;
}

void mem_cache::quota(size_t q)
{
	if (q < _quota) {
		auto_lock l(*this);
		_quota = q;
		enforce_quota();
	}
	else
		_quota = q;
}

void mem_cache::purge()
{
	size_t q = _quota;
	quota(0);
	quota(q);
}

struct equal_item_id {
	item_id const & id;
	equal_item_id(item_id_sptr const & i) : id(*i) { }
	bool operator () (cached_image_sptr const & i) const
		{ return i->id->equal(id); }
	bool operator () (item_id_sptr const & p) const
		{ return p->equal(id); }
};

void mem_cache::enforce_quota()
{
	//leave on disc those recently files that fit in quota
	lru_type::iterator i = lru.begin();
	while (_size > _quota && i != lru.end()) {
		if (std::find_if(loading_items.begin(), loading_items.end(), equal_item_id((*i)->id)) != loading_items.end()) {
			++i;
			continue;
		}
		_size -= (*i)->size();
		i = lru.erase(i);
	}
}

mem_cache::image_loading::image_loading(mem_cache & mc, item_id_sptr const & id, last_used_position new_lru_pos) : 
	c(mc), lru_pos(new_lru_pos)
{
	init(cached_image_sptr(new cached_image(id)));
}

mem_cache::image_loading::image_loading(mem_cache & mc, cached_image_sptr const & ci, last_used_position new_lru_pos) : 
	c(mc), lru_pos(new_lru_pos)
{
	init(ci);
}

void mem_cache::image_loading::init(cached_image_sptr const & ci)
{
	item_id_sptr const & id = ci->id;
	mswin::shared_resource::auto_lock l(c);
	c.wait_while_loading(id, l);

	i = std::find_if(c.lru.begin(), c.lru.end(), equal_item_id(id));
	if (i != c.lru.end()) {
		new_image = false;
		old_size = (*i)->size();
	} else {
		new_image = true;
		c.lru.push_front(ci);
		old_size = 0;
		i = c.lru.begin();
	}
	
	c.loading_items.push_front((*i)->id);
	j = c.loading_items.begin();
}

mem_cache::image_loading::~image_loading()
{
	mswin::shared_resource::auto_lock l(c);
	assert(i != c.lru.end());
	int delta = (*i)->size() - old_size;
	c._size += delta;
	c.reposition_element(i, lru_pos);
	c.loading_items.erase(j);
	c.image_loaded.pulse();
	if (delta > 0)
		c.enforce_quota();
}

void mem_cache::reposition_element(lru_type::iterator i, last_used_position p)
{
	switch (p) {
	case least_recently_used:
		if (i != lru.begin())
			lru.splice(lru.begin(), lru, i, ++lru_type::iterator(i));
		break;
	case dont_change_lru_position:
		break;
	case most_recently_used:
		lru.splice(lru.end(), lru, i, ++lru_type::iterator(i));
		break;
	case erase_element_as_unused:
		lru.erase(i);
		break;
	default:
		assert(false);
	}
}

void mem_cache::wait_while_loading(item_id_sptr const & id, auto_lock & l)
{
	while (std::find_if(loading_items.begin(), loading_items.end(), equal_item_id(id)) != loading_items.end()) {
		l.unlock();
		image_loaded.wait();
		l.lock();
	}
}

void mem_cache::put(cached_image_sptr const & c, last_used_position p)
{
	const item_id_sptr & id = c->id; 
	const byte_image & im = c->im;
	if (p == least_recently_used) {
		if (_size + im.size() > _quota)
			return;
	}
	assert(p != dont_change_lru_position && p != erase_element_as_unused);

	image_loading cl(*this, c, p);
}

cached_image_sptr mem_cache::get(item_id_sptr const & id, last_used_position p)
{
	assert(id->loadable());
	assert(p != dont_change_lru_position && p != erase_element_as_unused);

	image_loading cl(*this, id, p);
	if (cl.is_loading()) {
		///new element
		try {
			id->load(cl.e()->im);
		}
		catch (...) {
			cl.lru_pos = erase_element_as_unused;
			throw;
		}
	}

	return cl.e();
}

cached_image_sptr mem_cache::try_get(item_id_sptr const & id, last_used_position p)
{
	mswin::shared_resource::auto_lock l(*this);
	wait_while_loading(id, l);

	lru_type::iterator i = std::find_if(lru.begin(), lru.end(), equal_item_id(id));
	if (i == lru.end())
		return cached_image_sptr();

	reposition_element(i, p);
	return *i;
}

bool mem_cache::touch(item_id_sptr const & id, last_used_position p)
{
	mswin::shared_resource::auto_lock l(*this);
	if (std::find_if(loading_items.begin(), loading_items.end(), equal_item_id(id)) != loading_items.end())
		return true;

	lru_type::iterator i = std::find_if(lru.begin(), lru.end(), equal_item_id(id));
	if (i == lru.end())
		return false;

	reposition_element(i, p);
	return true;
}

void mem_cache::remove(item_id_sptr const & id)
{
	mswin::shared_resource::auto_lock l(*this);
	wait_while_loading(id, l);

	lru_type::iterator i = std::find_if(lru.begin(), lru.end(), equal_item_id(id));
	if (i == lru.end())
		return;

	_size -= (*i)->size();
	lru.erase(i);
}

int mem_cache::items()
{
	mswin::shared_resource::auto_lock l(*this);
	return lru.size();
}

} //namespace icache
